﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Farigola.Engine.SystemData;
using Farigola.Engine.SystemData.Entity.inst;
using Farigola.Engine.SystemData.Entity;
using Farigola.Engine.SystemData.Entity.Type;

namespace Farigola.Engine
{
    /// <summary>
    /// The tool to find entity in the entities of the project, from the root folder.
    /// </summary>
    public class EntityFinder
    {
        /// <summary>
        /// inst are system or user type.
        ///
        /// To used in a search, specify entities to scan.
        /// </summary>
        public enum FindSystemUserType
        {
            User,
            System,
            All,
        }

        /// <summary>
        /// for (only) builtin types and data.
        /// provided by the workspace.
        /// </summary>
        private SystemDataMgr _systemDataMgr;

        /// <summary>
        /// typeMgr for types created in the project
        /// (!) types and data are placed in the datatree under the root folder.
        /// the type mgr is only to accelerate accessing types.
        /// </summary>
        private TypeMgr _typeMgr;

        /// <summary>
        /// The root folder of data contained in the project.
        /// has no parent (= null).
        /// name is:  $$$prj_root 
        ///  
        /// has only 2 childs: 
        ///   $$$prj_root_system    system/internal data of the project
        ///   $$$prj_root_user        user data, types and entities
        /// </summary>
        private EntInstFolder _rootFolder;


        private EntInstFolder _rootSystemFolder;

        private EntInstFolder _rootUserFolder;

        //---------------------------------------------------------------------
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="systemDataMgr"></param>
        /// <param name="typeMgr"></param>
        public EntityFinder(SystemDataMgr systemDataMgr, TypeMgr typeMgr)
        {
            _systemDataMgr = systemDataMgr;
            _typeMgr = typeMgr;
        }

        //=====================================================================
        #region Checks methods.

        //---------------------------------------------------------------------
        /**
         * check the 3 root folders.
         * do a basic test.
         * @return
         */
        public bool CheckInternalAllRootFolder()
        {
            if (_rootFolder == null)
                return false;

            if (_rootSystemFolder == null)
                return false;
            if (_rootUserFolder == null)
                return false;
            return true;
        }

        //---------------------------------------------------------------------
        /**
         * check the internal content, after init.
         * 
    s	 *  a basic check, if an error occurs, return false.
         * @return
         */
        public bool CheckInternal()
        {
            if (_systemDataMgr == null)
                return false;

            if (_typeMgr == null)
                return false;

            // check the 3 root folders
            return CheckInternalAllRootFolder();
        }
        #endregion

        //=====================================================================
        #region get/set methods.

        //---------------------------------------------------------------------
        /**
         * provide the root folder to the entity finder of the project.
         * 
         * @param rootFolder
         * @return
         */
        public bool SetProjectRootFolder(EntInstFolder rootFolder)
        {
            // check the root folders
            if (rootFolder == null)
                return false;

            // set the root folder
            _rootFolder = rootFolder;

            // set 2 others root folder, user and system
            _rootSystemFolder = (EntInstFolder)_rootFolder.FindChildByName(EntityDef.prjRootSystemFolderName);
            _rootUserFolder = (EntInstFolder)_rootFolder.FindChildByName(EntityDef.prjRootUserFolderName);

            return true;
        }
        #endregion

        //=====================================================================
        #region match methods.

        //---------------------------------------------------------------------
        /**
         * Return true if the findSystUserType match with the typeSystOrUser.
         * 
         * @param findSystUserType
         * @param typeSystOrUser
         * @return
         */
        public bool MatchFindSystemUserType(FindSystemUserType findSystUserType, EntityDef.TypeSystOrUser typeSystOrUser)
        {
            if (findSystUserType == null)
                return true;
            if (typeSystOrUser == null)
                return false;

            // all and system accepts all cases of typeSystOrUser (syst and user)
            if (findSystUserType == FindSystemUserType.All || findSystUserType == FindSystemUserType.System)
                return true;

            // the FindSystemUserType.User accept only user, not system
            if (findSystUserType == FindSystemUserType.User && typeSystOrUser == EntityDef.TypeSystOrUser.User)
                return true;

            return false;
        }
        #endregion

        //=====================================================================
        #region find entity base methods.

        //---------------------------------------------------------------------
        /// <summary>
        /// find an entity type or instance it by the name, 
        /// start scan from the provided folder, and inside childs.
        /// </summary>
        /// <param name="findSystUserType"></param>
        /// <param name="startFolder"></param>
        /// <param name="name"></param>
        /// <param name="goInChilds"></param>
        /// <returns></returns>
        public EntityBase FindEntityByName(FindSystemUserType findSystUserType, EntInstFolder startFolder, String name, bool goInChilds)
        {
		    EntityBase foundEnt;
		    EntInstFolder fold;
		
		    if(startFolder == null)
			    return null;

		    // maybe the find concerns a built-in type or instance
		    foundEnt= _systemDataMgr.FindEntTypeByName(name);
		    if(foundEnt != null)
			    // don't check the SystOrUser for a built-in object
			    return foundEnt;
		
		    // first scan direct childs folder
		    foundEnt = startFolder.FindChildByName(name);
		
		    if(foundEnt != null){
			    // an entity match the name, check the sysUser type
			    if(MatchFindSystemUserType(findSystUserType, foundEnt.GetTypeSystOrUser()))
				    return foundEnt;
		    }
		
		    // not found in direct childs, go inside each childs?
		    if(!goInChilds)
			    return null;
			
		    // now scan inside each child folder
		    foreach(EntityBase ent in startFolder.GetListChild()) {
			    // is the child a folder?
			    if(ent is EntInstFolder){
				    fold= (EntInstFolder) ent;
				
				    // scan childs of the childs folders
				    foundEnt= FindEntityByName(findSystUserType, fold, name, goInChilds);
				    if(foundEnt != null)
					    // the inst was found by the name, no need to continue
					    return foundEnt;
			    }						
		    }

		    // not found
		    return null;
	    }

        //---------------------------------------------------------------------
        /**
         * find an entity instance it by the id, 
         * start scan from the provided folder, recursilvy.
         * 
         * @param name
         * @return
         */
        public EntityBase FindEntityById(FindSystemUserType findSystUserType, EntInstFolder startFolder, EntityId id, bool goInChilds)
        {
		    EntityBase foundEnt;
		    EntInstFolder fold;
		
		    if(startFolder == null)
			    return null;

		    // maybe the find concerns a built-in type or instance
		    foundEnt= _systemDataMgr.FindEntTypeById(id);
		    if(foundEnt != null)
			    // don't check the SystOrUser for a built-in object
			    return foundEnt;

		    // first scan direct childs folder
		    foundEnt = startFolder.FindChildById(id);
		
		    if(foundEnt != null){
			    // an entity match the name, check the sysUser type
			    if(MatchFindSystemUserType(findSystUserType, foundEnt.GetTypeSystOrUser()))
				    return foundEnt;
		    }
		
		    // not found in direct childs, go inside each childs?
		    if(!goInChilds)
			    return null;
		
		    // now scan inside each child folder
		    foreach(EntityBase ent in startFolder.GetListChild()) {
			    // is the child a folder?
			    if(ent is EntInstFolder){
				    fold= (EntInstFolder) ent;
				
				    // scan childs of the childs folders
				    foundEnt= FindEntityById(findSystUserType, fold, id, goInChilds);
				    if(foundEnt != null)
					    // the inst was found by the name, no need to continue
					    return foundEnt;
			    }						
		    }

		    // not found
		    return null;
	    }


        //---------------------------------------------------------------------
        /**
         * find an entity instance it by the name, 
         * start scan from the root folder, recursilvy.
         * scan all: system and user instance. 
         * 
         * @param name
         * @return
         */
        public EntityBase FindEntityByName(FindSystemUserType findSystUserType, String name) //, bool goInChilds)
        {
            // check the name
            if (name == null)
                return null;
            name = name.Trim();
            if (name.Length == 0)
                return null;

            // by default start from the root (scan in rootSystem and in rootUser)
            EntInstFolder startFolder = _rootFolder;

            // if scan only user, start from the root folder suer
            if (findSystUserType == FindSystemUserType.User)
                startFolder = _rootUserFolder;

            // start scan
            return FindEntityByName(FindSystemUserType.All, startFolder, name, true); //goInChilds);
        }

        //---------------------------------------------------------------------
        /**
         * find an entity instance it by the id, 
         * start scan from the root folder, recursilvy.
         * scan all: system and user instance. 
         * 
         * @param name
         * @return
         */
        public EntityBase FindEntityById(FindSystemUserType findSystUserType, EntityId id) //, bool goInChilds)
        {
            if (id == null)
                return null;
            if (!id.CheckInternal())
                return null;

            // by default start from the root (scan in rootSystem and in rootUser)
            EntInstFolder startFolder = _rootFolder;

            // if scan only user, start from the root folder suer
            if (findSystUserType == FindSystemUserType.User)
                startFolder = _rootUserFolder;

            // start scan
            return FindEntityById(FindSystemUserType.All, startFolder, id, true);
        }

        //---------------------------------------------------------------------
        /// <summary>
        /// Find an entity it by the name, 
        /// start scan from the root folder user, recursilvy.
        /// 
        /// scan only user instance. 
        /// don't scan entities instances under the root folder system.
        ///
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public EntityBase FindEntityByName(String name) //, bool goInChilds)
        {
            return FindEntityByName(FindSystemUserType.All, name); //, goInChilds);
        }

        #endregion

        //=====================================================================
        #region find entity type methods.

        //---------------------------------------------------------------------
        public EntTypeBase FindEntTypeBaseByName(FindSystemUserType findSystUser, String name) //, bool goInChilds)
        {

            EntityBase ent = FindEntityByName(findSystUser, name); //, goInChilds);

            // only if its a entity type base		
            if (ent is EntTypeBase)
                return (EntTypeBase)ent;

            return null;
        }

        //---------------------------------------------------------------------
        /// <summary>
        /// Find an entity type by the name.
        /// Search also in builtin type (like string, list,...)
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public EntTypeBase FindEntTypeBaseByName(String name)
        {
            return FindEntTypeBaseByName(FindSystemUserType.All, name); 
        }


        #endregion

        //=====================================================================
        #region find entity type class (and DM) methods.

        //---------------------------------------------------------------------
        public EntTypeClass FindEntTypeClassById(FindSystemUserType findSystUserType, EntInstFolder startFolder, EntityId id, bool goInChilds)
        {
            EntityBase ent = FindEntityById(findSystUserType, startFolder, id, goInChilds);

            if (ent is EntTypeClass)
                return (EntTypeClass)ent;
            return null;
        }

        //---------------------------------------------------------------------
        public EntTypeClass FindEntTypeClassByName(FindSystemUserType findSystUserType, EntInstFolder startFolder, String name, bool goInChilds)
        {
            EntityBase ent = FindEntityByName(findSystUserType, startFolder, name, goInChilds);

            if (ent is EntTypeClass)
                return (EntTypeClass)ent;
            return null;
        }


        //---------------------------------------------------------------------
        public EntTypeClassBase FindEntTypeClassBaseByName(FindSystemUserType findSystUser, String name) //, bool goInChilds)
        {		
		    EntityBase ent= FindEntityByName(findSystUser, name); //, goInChilds);
		
		    // only if its a entity type class base		
		    if(ent is EntTypeClassBase)
			    return (EntTypeClassBase)ent;
		
		    return null;			
	    }

        //---------------------------------------------------------------------
        public EntTypeClass FindEntTypeClassByName(FindSystemUserType findSystUser, String name) //, bool goInChilds)
        {		
		    EntTypeClassBase typeClassBase= FindEntTypeClassBaseByName(findSystUser, name); //, goInChilds);
		
		    // only if its a entity type class base		
		    if(typeClassBase is EntTypeClassBase)
			    return (EntTypeClass)typeClassBase;
		
		    return null;					
	    }

        //---------------------------------------------------------------------
        public EntTypeClass FindEntTypeClassByName(String name) //, bool goInChilds)
        {
            return FindEntTypeClassByName(FindSystemUserType.User, name); //, goInChilds);
        }


        //---------------------------------------------------------------------
        /**
         * Find the DM of the typeClass, by the name.
         * 
         * @param typeClass
         * @param nameDM
         * @return
         */
        public EntTypeClassDataMember FindTypeClassDataMemberByName(EntTypeClassBase typeClass, String nameDM)
        {
            if (typeClass == null)
                return null;

            // check the name, not null and not blank/empty
            // todo:

            return typeClass.FindDataMemberByName(nameDM);
        }

        #endregion


        //=====================================================================
        #region Find entity inst folder methods.

        //---------------------------------------------------------------------
        /// <summary>
        /// Find an entity instance folder it by the name, scan recursilvy
        /// start search under the root folder user.
        ///
        /// </summary>
        /// <param name="findSystUser"></param>
        /// <param name="parentFolder"></param>
        /// <param name="name"></param>
        /// <param name="goInChilds"></param>
        /// <returns></returns>
        public EntInstFolder FindEntInstFolderByName(FindSystemUserType findSystUser, EntInstFolder parentFolder, String name, bool goInChilds)
        {

            EntityBase inst = FindEntityByName(findSystUser, parentFolder, name, goInChilds);

            // only if its a folder		
            if (inst is EntInstFolder)
                return (EntInstFolder)inst;

            return null;
        }

        //---------------------------------------------------------------------
        /// <summary>
        /// Find an entity instance folder it by the name, scan in childs recursilvy or not.
        /// by default, find only User type,
        /// start from the parent folder.
        ///
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public EntInstFolder FindEntInstFolderByName(EntInstFolder parentFolder, String name, bool goInChilds)
        {
            return FindEntInstFolderByName(FindSystemUserType.All, parentFolder, name, goInChilds);
        }

        //---------------------------------------------------------------------
        /// <summary>
        /// Find an entity instance folder it by the name, scan recursilvy
        /// by default, find only User type,
        /// start from the parent folder.
        ///
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public EntInstFolder FindEntInstFolderByName(EntInstFolder parentFolder, String name)
        {
            return FindEntInstFolderByName(FindSystemUserType.All, parentFolder, name, true);
        }

        //---------------------------------------------------------------------
        /// <summary>
        /// Find an entity instance folder it by the name, scan recursilvy
        /// by default, find only User type,
        /// start from the root folder user.
        ///
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public EntInstFolder FindEntInstFolderByName(String name)
        {
            return FindEntInstFolderByName(FindSystemUserType.User, _rootUserFolder, name, true);
        }

        //---------------------------------------------------------------------
        /// <summary>
        /// Find an entity instance folder it by the id, scan recursilvy.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public EntInstFolder FindEntInstFolderById(EntityId id)
        {

            EntityBase inst = FindEntityById(FindSystemUserType.All, _rootUserFolder, id, true);

            // only if its a folder		
            if (inst is EntInstFolder)
                return (EntInstFolder)inst;

            return null;
        }

        #endregion


        //=====================================================================
        #region find ent inst basic (string, ...) methods.

        //---------------------------------------------------------------------
        /// <summary>
        /// find an entity instance string it by the name, scan recursilvy.
        /// </summary>
        /// <param name="findSystUser"></param>
        /// <param name="parentFolder"></param>
        /// <param name="name"></param>
        /// <param name="goInChilds"></param>
        /// <returns></returns>
        public EntInstString FindEntInstStringByName(FindSystemUserType findSystUser, EntInstFolder parentFolder,  String name, bool goInChilds)
        {

            EntityBase inst = FindEntityByName(findSystUser, parentFolder, name, goInChilds);

            // only if its a folder		
            if (inst is EntInstString)
                return (EntInstString)inst;

            return null;
        }

        //---------------------------------------------------------------------
        /// <summary>
        /// Find an entity instance string it by the name, scan recursilvy
        /// by default, find only User type,
        /// start from the root folder user.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public EntInstString FindEntInstStringByName(String name)
        {
            return FindEntInstStringByName(FindSystemUserType.User, _rootUserFolder, name, true);
        }

        //---------------------------------------------------------------------
        /// <summary>
        /// find an entity instance string it by the id, scan recursilvy.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public EntInstString FindEntInstStringById(EntityId id)
        {
            EntityBase inst = FindEntityById(FindSystemUserType.All, _rootUserFolder, id, true);

            // only if the type match
            if (inst is EntInstString)
                return (EntInstString)inst;

            return null;
        }

        #endregion


        //=====================================================================
        #region find entity inst class (and DM) methods.

        //---------------------------------------------------------------------
        /// <summary>
        /// Find the inst class, by the name, scan recursilvy.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="goInChilds"></param>
        /// <returns></returns>
        public EntInstClass FindEntInstClassByName(FindSystemUserType findSystUser, EntInstFolder parentFolder, String name, bool goInChilds)
        {
            EntityBase inst = FindEntityByName(FindSystemUserType.All, _rootUserFolder, name, goInChilds);

            // only if the type match
            if (inst is EntInstClass)
                return (EntInstClass)inst;

            return null;
        }

        //---------------------------------------------------------------------
        /// <summary>
        /// find the inst class, by the name, scan recursilvy.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="goInChilds"></param>
        /// <returns></returns>
        public EntInstClass FindEntInstClassByName(String name)
        {
            return FindEntInstClassByName(FindSystemUserType.All, _rootFolder, name, true);
	    }

        //---------------------------------------------------------------------
        /// <summary>
        /// find the inst class, by the name, scan recursilvy.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="goInChilds"></param>
        /// <returns></returns>
        public EntInstClassAnonym FindEntInstClassAnonymByName(FindSystemUserType findSystUser, EntInstFolder parentFolder, String name, bool goInChilds)
        {
            EntityBase inst = FindEntityByName(FindSystemUserType.All, _rootFolder, name, goInChilds);

            // only if the type match
            if (inst is EntInstClassAnonym)
                return (EntInstClassAnonym)inst;

            return null;
        }

        //---------------------------------------------------------------------
        /// <summary>
        /// find the inst class, by the name, scan recursilvy.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="goInChilds"></param>
        /// <returns></returns>
        public EntInstClassAnonym FindEntInstClassAnonymByName(String name)
        {
            return FindEntInstClassAnonymByName(FindSystemUserType.All, _rootFolder, name, true);
	    }

        //---------------------------------------------------------------------
        /// <summary>
        /// Find an inst data member in a inst class.
        /// </summary>
        /// <param name="instClass"></param>
        /// <param name="nameDM"></param>
        /// <returns></returns>
        public EntInstBase FindEntInstClassDataMemberByName(EntInstClassBase instClass, String nameDM)
        {
            if (instClass == null)
                return null;
            if (nameDM == null)
                return null;

            return instClass.FindEntInstDataMemberByName(nameDM);
        }
        #endregion

        //=====================================================================
        #region find entity inst struct (list,...) methods.

        //---------------------------------------------------------------------
        /// <summary>
        /// find the inst list, by the name, from the root folder, scan recursilvy.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public EntInstStructList FindEntInstStructListByName(FindSystemUserType findSystUser, EntInstFolder parentFolder, string name, bool goInChilds)
        {
            EntityBase inst = FindEntityByName(findSystUser, name);

            // only if the type match
            if (inst is EntInstStructList)
                return (EntInstStructList)inst;

            return null;

        }

        //---------------------------------------------------------------------
        /// <summary>
        /// find the inst list, by the name, from the root folder, scan recursilvy.
        /// 
        /// For instances, start scan from the root folder user by default.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public EntInstStructList FindEntInstStructListByName(string name)
        {
            return FindEntInstStructListByName(FindSystemUserType.All, _rootUserFolder, name, true);
        }

        #endregion
    }
}
